Commit 98c8f64f by keith

update

parent 1ade2ab3
...@@ -8,6 +8,9 @@ viewspath = "public" ...@@ -8,6 +8,9 @@ viewspath = "public"
# grpc port # grpc port
grpc_port = 8028 grpc_port = 8028
# 客服客户端前台地址
kefu_client_url = "http://localhost:8080"
# 用于本地服务静态资源地址 # 用于本地服务静态资源地址
static_host = "http://localhost:8080" static_host = "http://localhost:8080"
...@@ -26,6 +29,16 @@ mimc_HttpUrl = "https://mimc.chat.xiaomi.net/api/account/token" ...@@ -26,6 +29,16 @@ mimc_HttpUrl = "https://mimc.chat.xiaomi.net/api/account/token"
#HTTPSCertFile = "conf/ssl.crt" #HTTPSCertFile = "conf/ssl.crt"
#HTTPSKeyFile = "conf/ssl.key" #HTTPSKeyFile = "conf/ssl.key"
# Email
email_name = "玩车头条"
email_user = "cxq@cmp520.com"
email_pass = "Sr19890204"
email_host = "smtp.mxhichina.com"
email_port = "465"
# 是否开启工单邮件提醒
open_workorder_email = true
[dev] [dev]
httpaddr = "localhost" httpaddr = "localhost"
# 小米mimc配置信息(小米开放平台创建) # 小米mimc配置信息(小米开放平台创建)
......
...@@ -127,7 +127,7 @@ func (c *PublicController) Register() { ...@@ -127,7 +127,7 @@ func (c *PublicController) Register() {
"LastActivity": time.Now().Unix(), "LastActivity": time.Now().Unix(),
"Token": _md5Token, "Token": _md5Token,
}) })
user.Token = imTokenDto.Data.Token user.Token = _md5Token
} else { } else {
...@@ -150,7 +150,7 @@ func (c *PublicController) Register() { ...@@ -150,7 +150,7 @@ func (c *PublicController) Register() {
m5 := md5.New() m5 := md5.New()
m5.Write([]byte(imTokenDto.Data.Token)) m5.Write([]byte(imTokenDto.Data.Token))
_md5Token := hex.EncodeToString(m5.Sum(nil)) _md5Token := hex.EncodeToString(m5.Sum(nil))
user.Token = _md5Token
// update userinfo // update userinfo
c.UserRepository.Update(user.ID, orm.Params{ c.UserRepository.Update(user.ID, orm.Params{
"Token": _md5Token, "Token": _md5Token,
...@@ -445,7 +445,7 @@ func (c *PublicController) PushMessage() { ...@@ -445,7 +445,7 @@ func (c *PublicController) PushMessage() {
} }
// Upload upload image // Upload upload file
func (c *PublicController) Upload() { func (c *PublicController) Upload() {
// get user // get user
...@@ -476,6 +476,9 @@ func (c *PublicController) Upload() { ...@@ -476,6 +476,9 @@ func (c *PublicController) Upload() {
".JPG": true, ".JPG": true,
".JPEG": true, ".JPEG": true,
".PNG": true, ".PNG": true,
".zip": true,
".ZIP": true,
".mp4": true,
} }
if _, ok := AllowExtMap[ext]; !ok { if _, ok := AllowExtMap[ext]; !ok {
c.JSON(configs.ResponseFail, "上传失败,上传文件不合法!", nil) c.JSON(configs.ResponseFail, "上传失败,上传文件不合法!", nil)
...@@ -647,7 +650,8 @@ func (c *PublicController) ReplyWorkOrder() { ...@@ -647,7 +650,8 @@ func (c *PublicController) ReplyWorkOrder() {
// workorder exist // workorder exist
workOrderRepository := services.GetWorkOrderRepositoryInstance() workOrderRepository := services.GetWorkOrderRepositoryInstance()
if _, err := workOrderRepository.GetWorkOrder(workOrderComment.WID); err != nil { workOrder, err := workOrderRepository.GetWorkOrder(workOrderComment.WID)
if err != nil {
c.JSON(configs.ResponseFail, "发送失败,工单不存在!", nil) c.JSON(configs.ResponseFail, "发送失败,工单不存在!", nil)
} }
...@@ -665,6 +669,21 @@ func (c *PublicController) ReplyWorkOrder() { ...@@ -665,6 +669,21 @@ func (c *PublicController) ReplyWorkOrder() {
if !isUser { if !isUser {
status = 1 status = 1
params["LastReply"] = workOrderComment.AID params["LastReply"] = workOrderComment.AID
// send email message
openWorkorderEmail, _ := beego.AppConfig.Bool("open_workorder_email")
if workOrder.Email != "" && openWorkorderEmail {
go func() {
mailTo := []string{
workOrder.Email,
}
subject := "您的工单:" + workOrder.Title + "已被回复"
kefuClientURL := beego.AppConfig.String("kefu_client_url")
body := "工单标题:" + workOrder.Title + "<br>回复:" + workOrderComment.Content + "<br>您可以点<a target='_blank' href='" + kefuClientURL + "'>此链接</a>去查看完整内容"
utils.SendMail(mailTo, subject, body)
}()
}
} }
params["Status"] = status params["Status"] = status
if _, err := workOrderRepository.Update(workOrderComment.WID, params); err != nil { if _, err := workOrderRepository.Update(workOrderComment.WID, params); err != nil {
......
...@@ -5,8 +5,10 @@ import ( ...@@ -5,8 +5,10 @@ import (
"kefu_server/configs" "kefu_server/configs"
"kefu_server/models" "kefu_server/models"
"kefu_server/services" "kefu_server/services"
"kefu_server/utils"
"strconv" "strconv"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
"github.com/astaxie/beego/validation" "github.com/astaxie/beego/validation"
) )
...@@ -164,11 +166,27 @@ func (c *WorkOrderController) CloseWorkOrder() { ...@@ -164,11 +166,27 @@ func (c *WorkOrderController) CloseWorkOrder() {
c.JSON(configs.ResponseFail, err.Message, nil) c.JSON(configs.ResponseFail, err.Message, nil)
} }
} }
workOrder, err := c.WorkOrderRepository.GetWorkOrder(request.WID)
if err != nil {
c.JSON(configs.ResponseFail, "关闭失败,工单不存在或已关闭!", nil)
}
rows, err := c.WorkOrderRepository.Close(request.WID, auth.UID, request.Remark) rows, err := c.WorkOrderRepository.Close(request.WID, auth.UID, request.Remark)
if err != nil { if err != nil {
c.JSON(configs.ResponseFail, "关闭失败,出现异常!", nil) c.JSON(configs.ResponseFail, "关闭失败,出现异常!", nil)
} }
// send email message
openWorkorderEmail, _ := beego.AppConfig.Bool("open_workorder_email")
if workOrder.Email != "" && openWorkorderEmail {
go func() {
mailTo := []string{workOrder.Email}
kefuClientURL := beego.AppConfig.String("kefu_client_url")
emailName := beego.AppConfig.String("email_name")
subject := "您的工单:" + workOrder.Title + "已关闭"
body := "工单标题:" + workOrder.Title + "<br>您的工单已被关闭,如此问题还未得到解决,您可以重新进入<a target='_blank' href='" + kefuClientURL + "'>在线客服</a>以得到更多的帮助。<br>" + emailName
utils.SendMail(mailTo, subject, body)
}()
}
c.JSON(configs.ResponseSucess, "工单已关闭!", rows) c.JSON(configs.ResponseSucess, "工单已关闭!", rows)
} }
...@@ -30,6 +30,7 @@ func initLog() { ...@@ -30,6 +30,7 @@ func initLog() {
func main() { func main() {
end := make(chan bool, 1) end := make(chan bool, 1)
// init db // init db
db.Run() db.Run()
......
// @APIVersion 1.0.0 // @APIVersion 2.0.0
// @Title MIMC server API // @Title KEFU server API
// @Description kefu server APIs. // @Description kefu server APIs.
// @Contact 361554012@qq.com // @Contact 361554012@qq.com
package routers package routers
import ( import (
"kefu_server/controllers" "kefu_server/controllers"
...@@ -25,7 +25,7 @@ func init() { ...@@ -25,7 +25,7 @@ func init() {
AllowCredentials: true, AllowCredentials: true,
})) }))
ns := beego.NewNamespace("/v1", ns := beego.NewNamespace("/api",
// auth // auth
beego.NSNamespace("/auth", beego.NSNamespace("/auth",
......
...@@ -4337,11 +4337,6 @@ ...@@ -4337,11 +4337,6 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
}, },
"blueimp-md5": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.12.0.tgz",
"integrity": "sha512-zo+HIdIhzojv6F1siQPqPFROyVy7C50KzHv/k/Iz+BtvtVzSHXiMXOpq2wCfNkeBqdCv+V8XOV96tsEt2W/3rQ=="
},
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
...@@ -16414,6 +16409,11 @@ ...@@ -16414,6 +16409,11 @@
} }
} }
}, },
"vue-router": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz",
"integrity": "sha512-GYhn2ynaZlysZMkFE5oCHRUTqE8BWs/a9YbKpNLi0i7xD6KG1EzDqpHQmv1F5gXjr8kL5iIVS8EOtRaVUEXTqA=="
},
"vue-style-loader": { "vue-style-loader": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz",
...@@ -16467,6 +16467,11 @@ ...@@ -16467,6 +16467,11 @@
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw=="
}, },
"vuex": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.3.tgz",
"integrity": "sha512-k8vZqNMSNMgKelVZAPYw5MNb2xWSmVgCKtYKAptvm9YtZiOXnRXFWu//Y9zQNORTrm3dNj1n/WaZZI26tIX6Mw=="
},
"watchpack": { "watchpack": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
......
...@@ -10,13 +10,14 @@ ...@@ -10,13 +10,14 @@
"dependencies": { "dependencies": {
"axios": "^0.19.0", "axios": "^0.19.0",
"better-scroll": "^1.15.2", "better-scroll": "^1.15.2",
"blueimp-md5": "^2.12.0",
"core-js": "^2.6.5", "core-js": "^2.6.5",
"mint-ui": "^2.2.13", "mint-ui": "^2.2.13",
"moment": "^2.24.0", "moment": "^2.24.0",
"qiniu-js": "^2.5.5", "qiniu-js": "^2.5.5",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-photo-preview": "git+https://github.com/chenxianqi/vue-photo-preview.git", "vue-photo-preview": "git+https://github.com/chenxianqi/vue-photo-preview.git",
"vue-router": "^3.1.6",
"vuex": "^3.1.3",
"webpack": "^4.41.2" "webpack": "^4.41.2"
}, },
"devDependencies": { "devDependencies": {
......
import axios from "axios"; import axios from "axios";
import { Toast } from 'mint-ui'; import { Toast } from 'mint-ui';
var md5 = require('blueimp-md5')
var MimcPlugin = {}; var MimcPlugin = {};
MimcPlugin.install = function (Vue, options) { MimcPlugin.install = function (Vue, options) {
...@@ -20,11 +19,10 @@ MimcPlugin.install = function (Vue, options) { ...@@ -20,11 +19,10 @@ MimcPlugin.install = function (Vue, options) {
init(request, callback){ init(request, callback){
this.platform = request.platform this.platform = request.platform
this.fetchMIMCToken(request, callback) this.fetchMIMCToken(request, callback)
this.getRobot()
}, },
// 获取本地已经登录过的User // 获取本地已经登录过的User
getLocalCacheUser(uid){ getLocalCacheUser(){
const userString = localStorage.getItem("miniImAppUser_" + uid) const userString = localStorage.getItem("user")
if(userString) return JSON.parse(userString) if(userString) return JSON.parse(userString)
return null return null
}, },
...@@ -35,29 +33,25 @@ MimcPlugin.install = function (Vue, options) { ...@@ -35,29 +33,25 @@ MimcPlugin.install = function (Vue, options) {
axios.post('/public/register', request) axios.post('/public/register', request)
.then(response => { .then(response => {
this.fetchMIMCTokenResult = response.data.data.token this.fetchMIMCTokenResult = response.data.data.token
localStorage.setItem("miniImAppUser_" + response.data.data.user.id, JSON.stringify(response.data.data.user)) localStorage.setItem("user", JSON.stringify(response.data.data.user))
localStorage.setItem("Token", md5(response.data.data.user.token)) localStorage.setItem("Token", response.data.data.user.token)
console.log("MIMC初始化成功") console.log("MIMC初始化成功")
this.getRobot()
if(callback) callback(response.data.data.user) if(callback) callback(response.data.data.user)
}) })
.catch((error)=>{ .catch((error)=>{
if(callback) callback(null) if(callback) callback(null)
console.log(error.response) console.log(error.response)
Toast({
message: error.response.data.message
})
}) })
}, },
// 获取机器人 // 获取机器人
getRobot(){ getRobot(){
axios.get('/public/robot/1') axios.get('/public/robot/'+this.platform)
.then(response => { .then(response => {
this.robot = response.data.data this.robot = response.data.data
}) })
.catch((error)=>{ .catch((error)=>{
Toast({ console.log("mimc初始化失败,请刷新重试", error)
message: "mimc初始化失败,请刷新重试" + error.response.data.message
})
}) })
}, },
// pushMessage // pushMessage
......
import Vue from 'vue' import Vue from 'vue'
import App from './App.vue' import App from './App.vue'
import preview from 'vue-photo-preview' import preview from 'vue-photo-preview'
import router from "./router"
import store from './store'
import 'vue-photo-preview/dist/skin.css' import 'vue-photo-preview/dist/skin.css'
import MintUI from 'mint-ui' import MintUI from 'mint-ui'
import 'mint-ui/lib/style.css' import 'mint-ui/lib/style.css'
...@@ -12,7 +14,7 @@ moment.locale("zh-cn", momentLocal) ...@@ -12,7 +14,7 @@ moment.locale("zh-cn", momentLocal)
import axios from 'axios' import axios from 'axios'
axios.defaults.baseURL = '/v1' axios.defaults.baseURL = '/api'
// axios添加请求拦截器 // axios添加请求拦截器
axios.interceptors.request.use(function (config) { axios.interceptors.request.use(function (config) {
...@@ -37,5 +39,7 @@ Vue.use(MimcPlugin) ...@@ -37,5 +39,7 @@ Vue.use(MimcPlugin)
Vue.use(MintUI) Vue.use(MintUI)
Vue.config.productionTip = false Vue.config.productionTip = false
new Vue({ new Vue({
router,
store,
render: h => h(App) render: h => h(App)
}).$mount('#app') }).$mount('#app')
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'kefu',
component: () => import('./views/kefu.vue')
},
{
path: '/workorder',
name: 'workorder',
component: () => import('./views/workorder.vue')
},
{
path: '/workorder/create',
name: 'workorder_create',
component: () => import('./views/workorder_create.vue')
},
{
path: '/workorder/detail',
name: 'workorder_detail',
component: () => import('./views/workorder_detail.vue')
},
]
})
export default router
const axios = require('axios')
export default {
ON_CHANGE_CAR_LIST(context, params) {
context.commit('onChangeCarList', params)
},
}
\ No newline at end of file
export default {
platform(state) {
return state.platform;
},
isArtificial(state) {
return state.isArtificial;
},
isShowHeader(state) {
return state.isShowHeader;
},
isMobile(state) {
return state.isMobile;
},
uid(state) {
return state.uid;
},
userAccount(state) {
return state.userAccount;
},
artificialAccount(state) {
return state.artificialAccount;
},
robotInfo(state) {
return state.robotInfo
},
robotAccount(state) {
return state.robotAccount
}
}
\ No newline at end of file
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
import getters from './getters'
import state from './state'
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
getters: getters,
mutations: mutations,
actions: actions
})
\ No newline at end of file
export default {
updateState(state, newObj){
for (var i in newObj) {
if(newObj[i] == undefined) continue
state[i] = newObj[i]
}
}
}
\ No newline at end of file
export default {
platform: 5, // 平台(渠道)
isShowHeader: true, // 是否显示header
isMobile: true, // 是否是移动端
isArtificial: false, // 是否是人工服务
uid: 0, // 业务平台的ID
userAccount: 0, // 用户账号
artificialAccount: null, // 客服账号ID
robotInfo: null, // 机器人信息
robotAccount: null, // 机器人账号ID
}
\ No newline at end of file
<template>
<div class="container">
<mt-header v-if="isShowHeader" fixed :title="isInputPongIng ? inputPongIngString : '在线客服'">
<div slot="left">
<mt-button @click="back" icon="back"></mt-button>
</div>
<mt-button @click="headRightBtn" slot="right">
<img title="人工客服" v-if="!isArtificial" src="http://qiniu.cmp520.com/kefu_icon_2000.png" alt />
<span v-else>结束会话</span>
</mt-button>
</mt-header>
</div>
</template>
<script>
export default {
name: "workorder",
components: {},
data() {
return {};
},
mounted() {},
methods: {}
};
</script>
<style lang="stylus" scoped>
</style>
<template>
<div class="container">
workorder_create
</div>
</template>
<script>
export default {
name: "workorder_create",
components: {},
data() {
return {};
},
mounted() {},
methods: {}
};
</script>
<style lang="stylus" scoped>
</style>
<template>
<div class="container">
workorder_detail
</div>
</template>
<script>
export default {
name: "workorder_detail",
components: {},
data() {
return {};
},
mounted() {},
methods: {}
};
</script>
<style lang="stylus" scoped>
</style>
package utils
import (
"strconv"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"gopkg.in/gomail.v2"
)
// SendMail send email
func SendMail(mailTo []string, subject string, body string) {
mailConn := map[string]string{
"user": beego.AppConfig.String("email_user"),
"pass": beego.AppConfig.String("email_pass"),
"host": beego.AppConfig.String("email_host"),
"port": beego.AppConfig.String("email_port"),
}
port, _ := strconv.Atoi(mailConn["port"])
m := gomail.NewMessage()
m.SetHeader("From", m.FormatAddress(mailConn["user"], beego.AppConfig.String("email_name")))
m.SetHeader("To", mailTo...)
m.SetHeader("Subject", subject)
m.SetBody("text/html", body)
d := gomail.NewDialer(mailConn["host"], port, mailConn["user"], mailConn["pass"])
err := d.DialAndSend(m)
if err != nil {
logs.Error("SendMail send email error------------", err)
}
logs.Info("SendMail send email success------------", mailTo)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment